using System.Collections;
using System.Linq;
using Arcane.Source.Client.Extensions;
using Arcane.Source.Data;
using Arcane.Source.TileMatch;
using Arcane.Source.Utils;
using Godot;

namespace Arcane.Source.Client;

public partial class TileMatcher : Control
{
    private TileCell[,] _cells = null;
    public float CellSize { get; set; } = 50;
    public Vector2 CellExtent => new(CellSize, CellSize);

    private TilePos? _selected = null;
    private TilePos? _target = null;

    private readonly CoroutineExecutor _coroutines = new();
    private Texture2D[,] _runeTextures;

    private Match _match;

    public override void _Notification(int what)
    {
        if (what == NotificationResized)
        {
            var f = GetRect();

            var fw = f.Size.X;
            var fh = f.Size.Y;

            var a = (double)Rule.COL / Rule.ROW;
            var fa = (double)fw / fh;

            CellSize = fa > a ? (float)(fh / Rule.ROW) : (float)(fw / Rule.COL);

            if (_cells == null) return;

            for (int r = 0; r < Rule.ROW; r++)
            {
                for (int c = 0; c < Rule.COL; c++)
                {
                    _cells[c, r].Position = GetCellPivot((c, r));
                    _cells[c, r].Resize(CellSize);
                }
            }
        }
    }

    public override void _Draw()
    {
        for (var col = 0; col < Rule.COL; col++)
        {
            for (var row = 0; row < Rule.ROW; row++)
            {
                var pos = new Vector2(col, row) * CellExtent;
                var color = (col + row) % 2 == 0 ? Colors.DimGray : Colors.DarkGray;

                DrawRect(new Rect2(pos, CellExtent), color);
            }
        }
    }

    public override void _Input(InputEvent @event)
    {
        if (@event is InputEventKey ke)
        {
            switch (ke.Keycode)
            {
                case Key.Space:
                    if (ke.Pressed)
                    {
                        ShowHint();
                    }
                    break;
            }
        }
    }

    public override void _GuiInput(InputEvent @event)
    {
        switch (@event)
        {
            case InputEventMouseButton mbe:
                HandleMouseButton(mbe.Position, mbe.Pressed, mbe.ButtonIndex);
                break;

            case InputEventMouseMotion mme:
                HandleMouseMotion(mme.Position);
                break;
        }
    }

    public override void _Process(double delta)
    {
        _coroutines.Tick();
    }

    public void Init(Match match, StringName[] runeIds)
    {
        _match = match;
        InitRuneTextures(runeIds);

        _cells = new TileCell[Rule.COL, Rule.ROW];

        for (var row = 0; row < Rule.ROW; row++)
        {
            for (var col = 0; col < Rule.COL; col++)
            {
                var cell = new TileCell(GetCellPivot((col, row)));
                cell.Resize(CellSize);
                cell.Sync(match.GetTileUnchecked(col, row), this);
                cell.ZIndex = 1;
                _cells[col, row] = cell;
                AddChild(cell);
            }
        }
    }

    private void InitRuneTextures(StringName[] runeIds)
    {
        _runeTextures = new Texture2D[Element.Size.AsInt(), Rule.MAX_LEVEL];
        for (var ele = 0; ele < runeIds.Length; ele++)
        {
            var id = runeIds[ele];
            if (id == null) continue;

            var shape = GameDB.Element.Get((Element)ele).Shape.GetImage();
            var rune = GameDB.Rune.Get(id);

            for (var lvl = 0; lvl < Rule.MAX_LEVEL; lvl++)
            {
                var img = Image.CreateEmpty(18, 18, false, Image.Format.Rgba8);

                img.Stack(shape, 1, 1);
                img.Stack(rune.GetTexture(lvl).GetImage(), 1, 1);

                img.GenOutline(Colors.Black);

                _runeTextures[ele, lvl] = ImageTexture.CreateFromImage(img);
            }
        }
    }

    public void HandleMouseButton(Vector2 position, bool pressed, MouseButton btn)
    {
        var (col, row) = (Mathf.FloorToInt(position.X / CellSize), Mathf.FloorToInt(position.Y / CellSize));

        if (btn == MouseButton.Left)
        {
            if (pressed)
            {
                if (Rule.ColValid(col) && Rule.RowValid(row))
                {
                    if (_selected is TilePos pos)
                    {
                        _cells[pos.col, pos.row].Scale = 1.0f * Vector2.One;
                        _cells[pos.col, pos.row].ZIndex--;
                    }

                    _cells[col, row].Scale = 1.2f * Vector2.One;
                    _cells[col, row].ZIndex++;
                    _selected = (col, row);
                }
            }
            else
            {
                if (_selected is TilePos selected)
                {
                    _selected = null;
                    if (_target is TilePos target)
                    {
                        _target = null;
                        _coroutines.StartCoroutine(DoSwap(selected, target));
                    }
                    else
                    {
                        _cells[selected.col, selected.row].MoveTo(GetCellPivot(selected));
                        _cells[selected.col, selected.row].Scale = 1.0f * Vector2.One;
                        _cells[selected.col, selected.row].ZIndex--;
                    }
                }
            }
        }
    }

    private IEnumerator DoSwap(TilePos selected, TilePos target)
    {
        while (_cells[target.col, target.row].Animating) yield return null;

        if (!_match.TrySwapTile(selected, target))
        {
            _cells[target.col, target.row].MoveTo(GetCellPivot(target));
            _cells[selected.col, selected.row].MoveTo(GetCellPivot(selected));
        }

        _cells[selected.col, selected.row].Scale = 1.0f * Vector2.One;
        _cells[selected.col, selected.row].ZIndex--;
    }

    public void HandleMouseMotion(Vector2 position)
    {
        if (_selected is TilePos selected)
        {
            TilePos mouse = (Mathf.FloorToInt(position.X / CellSize), Mathf.FloorToInt(position.Y / CellSize));
            var closest = _match.GetSwapables(selected).Append(selected).MinBy(p => p.SqDistance(mouse));

            if (closest != _target)
            {
                _cells[selected.col, selected.row].InstantMoveTo(GetCellPivot(closest));

                if (_target is TilePos target)
                {
                    _cells[target.col, target.row].MoveTo(GetCellPivot(target));
                    _target = null;
                }

                if (closest != selected)
                {
                    _cells[closest.col, closest.row].MoveTo(GetCellPivot(selected));
                    _target = closest;
                }
            }
        }
    }

    public void ShowHint()
    {
        if (_match.Hints is var (a, b))
        {
            _cells[a.col, a.row].HintTo(CellSize * b.ToCellCenter());
            _cells[b.col, b.row].HintTo(CellSize * a.ToCellCenter());
        }
    }


    public Texture2D GetElementTexture(Element element, int level)
    {
        return _runeTextures[element.AsInt(), level];
    }

    public Vector2 GetCellPivot(TilePos pos)
    {
        return CellSize * pos.ToCellCenter();
    }

    public void OnTileRemoved(ITile _, TilePos pos)
    {
        _cells[pos.col, pos.row].Sync(null, this);
    }

    public void OnCellUpdate(ITile tile, TilePos pos)
    {
        _cells[pos.col, pos.row].Sync(tile, this);
        _cells[pos.col, pos.row].Position = GetCellPivot(pos);
    }

    public TileCell GetCell(TilePos pos) => _cells[pos.col, pos.row];
}